<?php

namespace Zaya\Lib\SSO;

use Illuminate\Http\Response;

class Client extends Base
{
    private $serverUrl;
    private $clientId;
    private $clientSecret;

    public function __construct()
    {
        $this->serverUrl = config('sso.client.server_gateway');
        $this->clientId = config('sso.client.id');
        $this->clientSecret = config('sso.client.secret');
    }

    /**
     * 将 SSO Client Token 绑定到 SSO Server Token
     *
     * @return Response
     */
    public function attach()
    {
        $params = ['return_url' => url()->current()];
        $url = $this->getRequestUrl('attach', $params);
        return response()->redirectTo($url);
    }

    /**
     * @param string $username
     * @param string $password
     * @return array|object
     * @throws NotAttachedException|NotLoginException|Exception
     */
    public function login($username, $password)
    {
        if (!isset($username) && isset($_POST['username'])) {
            $username = $_POST['username'];
        }
        if (!isset($password) && isset($_POST['password'])) {
            $password = $_POST['password'];
        }

        $userInfo = $this->request('POST', 'login', compact('username', 'password'));
        return $userInfo;
    }

    /**
     * 判断 SSO Server 是否为登录状态
     * @return bool
     */
    public function isLogin() {

        $params = [
            'command' => 'userInfo',
            'client_id' => $this->clientId,
            'client_token' => $this->getClientToken(),
        ];
        $this->generateChecksum($params);

        try {
            $this->request('GET', 'userInfo', $params);
        } catch (\Exception $e) {
            return false;
        }
        return true;
    }

    /**
     * @return array|object
     * @throws NotAttachedException|NotLoginException|Exception
     */
    public function logout()
    {
        $params = [
            'command' => 'logout',
            'client_id' => $this->clientId,
            'client_token' => $this->getClientToken(),
        ];
        $this->generateChecksum($params);
        return $this->request('POST', 'logout', $params);
    }

    /**
     * @return array|object
     * @throws NotAttachedException|NotLoginException|Exception
     */
    public function getUserInfo()
    {
        if (!isset($this->userInfo)) {
            $params = [
                'command' => 'userInfo',
                'client_id' => $this->clientId,
                'client_token' => $this->getClientToken(),
            ];
            $this->generateChecksum($params);
            $this->userInfo = $this->request('GET', 'userInfo', $params);
        }

        return $this->userInfo;
    }

    /**
     * 请求 SSO Server
     *
     * @param string $method HTTP method: 'GET', 'POST', 'DELETE'
     * @param string $command Command
     * @param array|string $data Query or post parameters
     * @return array|object
     * @throws NotAttachedException|NotLoginException|Exception
     */
    protected function request($method, $command, $data = null)
    {
        if (!$this->isAttached()) {
            throw new NotAttachedException($this, 'No SSO Client Token');
        }
        $url = $this->getRequestUrl($command, !$data || $method === 'POST' ? [] : $data);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
//        curl_setopt($ch, CURLOPT_PROXY, 'http://192.168.1.100:8888');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-Requested-With: XMLHttpRequest',
        ]);

        if (strpos($url, 'https') === 0) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        }

        if ($method === 'POST' && !empty($data)) {
            $post = is_string($data) ? $data : http_build_query($data);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
        }

        $response = curl_exec($ch);
        if (curl_errno($ch) != 0) {
            $message = 'Server request failed: ' . curl_error($ch);
            throw new Exception($message);
        }

        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        list($contentType) = explode(';', curl_getinfo($ch, CURLINFO_CONTENT_TYPE));

        if ($contentType != 'application/json') {
            $message = 'Expected application/json response, got ' . $contentType;
            throw new Exception($message);
        }

        $data = json_decode($response, true);
        if ($httpCode == NotAttachedException::$status) {
            $this->clearClientToken();
            throw new NotAttachedException($this, $data['message'] ?? $response);
        }
        if ($httpCode == NotLoginException::$status) {
            throw new NotLoginException($this, $data['message'] ?? $response);
        }
        if ($httpCode >= 400) {
            throw new Exception($data['message'] ?? $response, $httpCode);
        }

        return $data;
    }

    /**
     * Get URL to attach session at SSO server.
     *
     * @param array $params
     * @return string
     */
    public function getAttachUrl($params = [])
    {
        $params = [
                'command' => 'attach',
                'client_id' => $this->clientId,
                'client_token' => $this->getClientToken(),
            ] + $params;
        $this->generateChecksum($params);

        return $this->serverUrl . '?' . http_build_query($params);
    }

    /**
     * 获取 SSO Client Token
     */
    public function getClientToken()
    {
        static $token;
        if ($token) {
        } elseif ($token = $_COOKIE['sso_token'] ?? null) {
        } else {
            if (session('sso_token_last_time', 0) < time() - 300) {
                // 上次生成sso_token离现在超过五分钟，重新生成
                $token = base_convert(md5(uniqid(rand(), true)), 16, 36);
                session([
                    'sso_token_last_time' => time(),
                    'sso_token' => $token,
                ]);
                session()->save();
            } else {
                $token = session('sso_token');
            }
            // 30天
            setcookie('sso_token', $token, time() + 2592000, '/');
        }
        return $token;
    }

    public function clearClientToken()
    {
        setcookie('sso_token', null, 1, '/');
    }

    public function isAttached()
    {
        return isset($_COOKIE['sso_token']);
    }

    /**
     * Get the request url for a command
     *
     * @param string $command
     * @param array $params Query parameters
     * @return string
     */
    public function getRequestUrl($command, $params = [])
    {
        $params['command'] = $command;
        if (!isset($params['client_id'])) {
            $params['client_id'] = $this->clientId;
        }
        if (!isset($params['client_token'])) {
            $params['client_token'] = $this->getClientToken();
        }
        if (!isset($params['checksum'])) {
            $this->generateChecksum($params);
        }
        return $this->serverUrl . '?' . http_build_query($params);
    }

    /**
     * Generate session id from session token
     *
     * @param array $params
     * @return string
     */
    protected function generateChecksum(&$params)
    {
        return $params['checksum'] = $this->generateChecksumSub($params['command'], $params['client_token'],
            $this->clientSecret);
    }
}
